﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
using TemplateEngineWithChart.Docx.Errors;
using TemplateEngineWithChart.Docx.Processors.ProcessResults;

namespace TemplateEngineWithChart.Docx.Processors
{
    internal class ListProcessor : IProcessor
    {
        private bool _isNeedToRemoveContentControls;
        private readonly ProcessContext _context;

        public ListProcessor(ProcessContext context)
        {
            _context = context;
        }
        public IProcessor SetRemoveContentControls(bool isNeedToRemove)
        {
            _isNeedToRemoveContentControls = isNeedToRemove;
            return this;
        }

        public ProcessResult FillContent(XElement contentControl, IEnumerable<IContentItem> items)
        {
            var processResult = ProcessResult.NotHandledResult;
            var handled = false;

            foreach (var contentItem in items)
            {
                var itemProcessResult = FillContent(contentControl, contentItem);
                processResult.Merge(itemProcessResult);

                if (!itemProcessResult.Handled) continue;

                handled = true;
            }

            if (!handled) return processResult;

            if (!processResult.Success || !_isNeedToRemoveContentControls) return processResult;

            foreach (var sdt in contentControl.Descendants(W.sdt).ToList())
            {
                // Remove the content control, and replace it with its contents.
                sdt.RemoveContentControl();
            }
            contentControl.RemoveContentControl();
            return processResult;
        }

        private ProcessResult FillContent(XElement contentControl, IContentItem item)
        {
            var processResult = ProcessResult.NotHandledResult;
            if (!(item is ListContent))
            {
                return ProcessResult.NotHandledResult;
            }

            var list = item as ListContent;

            if (contentControl != null)
            {
                var itemsContentControl = contentControl
                    .Descendants(W.sdt)
                    .FirstOrDefault();

                if (itemsContentControl != null)
                {
                    if (list.IsHidden || list.FieldNames == null)
                    {
                        contentControl.Descendants(W.tr).Remove();
                    }
                    else
                    {
                        var fieldNames = list.FieldNames.ToList();

                        // Create a prototype of new items to be inserted into the document.
                        var prototype = new Prototype(_context, contentControl, fieldNames);

                        if (prototype.IsValid)
                        {
                            new NumberingAccessor(_context.Document.NumberingPart, _context.LastNumIds)
                                .ResetNumbering(prototype.PrototypeItems);

                            // Propagates a prototype.
                            if (list.Items != null)

                            {
                                var propagationResult = PropagatePrototype(prototype, list.Items);
                                processResult.Merge(propagationResult);
                                // add all of the newly constructed rows.
                                if (!item.IsHidden) prototype.PrototypeItems.Last().AddAfterSelf(propagationResult.Result);
                            }

                            prototype.PrototypeItems.Remove();
                        }
                    }
                }
            }

            processResult.AddItemToHandled(list);

            return processResult;
        }

        // Fills prototype with values recursive.
        private PropagationProcessResult PropagatePrototype(Prototype prototype,
            IEnumerable<ListItemContent> content)
        {
            var processResult = new PropagationProcessResult();
            var newRows = new List<XElement>();

            foreach (var contentItem in content)
            {
                var currentLevelPrototype = prototype.CurrentLevelPrototype(contentItem.FieldNames);

                if (currentLevelPrototype == null || !currentLevelPrototype.IsValid)
                {
                    processResult.AddError(new CustomError(
                        string.Format("Prototype for list item '{0}' not found",
                            string.Join(", ", contentItem.FieldNames))));

                    continue;
                }

                // Create new item from the prototype.
                var newItemEntry = currentLevelPrototype.Clone();

                foreach (var xElement in newItemEntry.PrototypeItems)
                {
                    var newElement = new XElement(xElement);
                    if (!newElement.DescendantsAndSelf(W.sdt).Any())
                    {
                        newRows.Add(newElement);
                        continue;
                    }

                    foreach (var sdt in newElement.FirstLevelDescendantsAndSelf(W.sdt).ToList())
                    {
                        var fieldContent = contentItem.GetContentItem(sdt.SdtTagName());
                        if (fieldContent == null)
                        {
                            processResult.AddError(new CustomError(
                                string.Format("Field content for field '{0}' not found",
                                sdt.SdtTagName())));

                            continue;
                        }

                        var contentProcessResult = new ContentProcessor(_context)
                            .SetRemoveContentControls(_isNeedToRemoveContentControls)
                            .FillContent(sdt, fieldContent);

                        processResult.Merge(contentProcessResult);


                    }
                    newRows.Add(newElement);

                }

                // If there are nested items fill prototype for them.
                if (contentItem.NestedFields != null)
                {
                    var filledNestedFields = PropagatePrototype(
                        prototype.Exclude(currentLevelPrototype),
                        contentItem.NestedFields);

                    newRows.AddRange(filledNestedFields.Result);
                }
            }
            processResult.Result = newRows;
            return processResult;
        }
    }
}
